﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using BattlelogMobile.Core.Message;
using BattlelogMobile.Core.Model;
using GalaSoft.MvvmLight.Messaging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace BattlelogMobile.Core.Service
{
    public class JSONParser : IJSONParser<ISoldier>
    {
        private const string JSONParseFailedMessage = "Couldn't read soldier information";
        private readonly IsolatedStorageFile _isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication();

        /// <summary>
        /// Here lies some nasty JSON parsing...
        /// </summary>
        /// <returns></returns>
        public ISoldier Parse()
        {
            IsolatedStorageFileStream resource = null;
            try
            {
                resource = _isolatedStorage.OpenFile(Common.OverviewFile, FileMode.Open);
                var streamReader = new StreamReader(resource);
            
                string completeJson = streamReader.ReadToEnd();
                JObject jObject = JObject.Parse(completeJson);
                var dataToken = jObject.SelectToken("data");
                var overviewStatsToken = dataToken.SelectToken("overviewStats");

                ISoldier soldier = ParseSoldier(overviewStatsToken);
                soldier.User = ParseUser(dataToken.SelectToken("user"));

                var serviceStars = ParseServiceStars(overviewStatsToken.SelectToken("serviceStars"));
                soldier.KitProgressions = ParseKitServiceStarProgressions(overviewStatsToken.SelectToken("serviceStarsProgress"), serviceStars);
                soldier.Score = ParseScore(overviewStatsToken);
                soldier.Score.Kits = ParseKitScore(overviewStatsToken);
                soldier.TopWeapons = ParseTopWeapons(dataToken);
                soldier.Awards = ParseAwards(dataToken.SelectToken("awards"));
                soldier.Statistics = ParseStatistics(dataToken.SelectToken("overviewStats"));

                soldier.TopVehicles = ParseTopVehicles(Common.VehiclesFile);
                soldier.TopGadgets = ParseTopGadgets(Common.GadgetsFile);

                return soldier;
            }
            catch (Exception e)
            {
                if (e is JsonReaderException || e is IOException || e is ArgumentException || e is FormatException || e is NullReferenceException)
                    Messenger.Default.Send(new BattlelogResponseMessage(JSONParseFailedMessage, false));
                else
                    throw;
                return null;
            }
            finally
            {
                if (resource != null)
                    resource.Close();
            }
        }

        // Kit scores, usage time and usage percentage
        private IKits ParseKitScore(JToken scoreToken)
        {
            var kits = new List<IKit>();
            // Kit score
            foreach (var kit in scoreToken.SelectToken("kitScores"))
            {
                string[] parts = kit.ToString().Split(':');
                var kitType = (KitType)Convert.ToInt32(parts[0].Substring(parts[0].IndexOf('"') + 1, parts[0].LastIndexOf('"') - 1));
                int kitScore = Convert.ToInt32(parts[1].Substring(parts[1].IndexOf('"') + 1, parts[1].LastIndexOf('"') - 2));
                kits.Add(new Kit(kitType, kitScore));   
            }

            // Do very unique, custom sort ;)
            kits.Sort((p1, p2) =>
                string.CompareOrdinal(
                    ((int)p1.Type).ToString(CultureInfo.InvariantCulture).Substring(0, 1),
                        ((int)p2.Type).ToString(CultureInfo.InvariantCulture).Substring(0, 1)));

            // Kit usage times
            foreach (var kit in scoreToken.SelectToken("kitTimes"))
            {
                string[] parts = kit.ToString().Split(':');
                var kitType = (KitType)Convert.ToInt32(parts[0].Substring(parts[0].IndexOf('"') + 1, parts[0].LastIndexOf('"') - 1));
                var kitTime = TimeSpan.FromSeconds(
                    Convert.ToDouble(parts[1].Substring(parts[1].IndexOf('"') + 1, parts[1].LastIndexOf('"') - 2),
                        CultureInfo.InvariantCulture));
                (kits.First(kitScore => kitScore.Type == kitType)).Time = kitTime;
            }

            // Kit usage percentage
            foreach (var kit in scoreToken.SelectToken("kitTimesInPercentage"))
            {
                string[] parts = kit.ToString().Split(':');
                var kitType = (KitType)Convert.ToInt32(parts[0].Substring(parts[0].IndexOf('"') + 1, parts[0].LastIndexOf('"') - 1));
                var kitPercentage = Convert.ToDouble(parts[1], CultureInfo.InvariantCulture);
                (kits.First(kitScore => kitScore.Type == kitType)).Percentage = kitPercentage;
            }
            return new Kits(kits);
        }

        // Total score and "generic" multiplayer scores
        private IScore ParseScore(JToken scoreToken)
        {
            IScore score = new Score();
            int totalScore = Convert.ToInt32(scoreToken.SelectToken("totalScore").ToString());
            score.TotalScore = totalScore;

            IOthers others = new Others(
                new List<IOther>()
                    {
                        new Other("Vehicles", Convert.ToInt32(scoreToken.SelectToken("sc_vehicle").ToString())), 
                        new Other("Combat", Convert.ToInt32(scoreToken.SelectToken("combatScore").ToString())),
                        new Other("Awards", Convert.ToInt32(scoreToken.SelectToken("sc_award").ToString())),
                        new Other("Unlocks", Convert.ToInt32(scoreToken.SelectToken("sc_unlock").ToString()))
                    });
            
            score.Others = others;
            return score;
        }

        // Combine earned stars and current progress
        private IKitProgressions ParseKitServiceStarProgressions(IEnumerable<JToken> kitServiceStarProgressToken, List<KeyValuePair<KitType, int>> serviceStars)
        {
            // Stars and current progress are on different tokens
            var progressions = new List<IKitProgression>();
            foreach (var kitServiceStarProgress in kitServiceStarProgressToken)
            {
                string[] parts = kitServiceStarProgress.ToString().Split(':');
                var kitType = (KitType)Convert.ToInt32(parts[0].Substring(parts[0].IndexOf('"') + 1, parts[0].LastIndexOf('"') - 1));
                double kitProgress = Convert.ToDouble(parts[1], CultureInfo.InvariantCulture);
                
                int numberOfServiceStars = 0;
                foreach (var serviceStar in serviceStars.Where(serviceStar => serviceStar.Key == kitType))
                    numberOfServiceStars = serviceStar.Value;
                progressions.Add(new KitProgression(kitType, numberOfServiceStars, kitProgress));
            }
            progressions.Sort((p1, p2) =>
                string.CompareOrdinal(
                    ((int)p1.Type).ToString(CultureInfo.InvariantCulture).Substring(0, 1),
                        ((int)p2.Type).ToString(CultureInfo.InvariantCulture).Substring(0, 1)));
            return new KitProgressions(progressions);
        }

        // Number of service stars
        private List<KeyValuePair<KitType, int>> ParseServiceStars(IEnumerable<JToken> serviceStarsToken)
        {
            var serviceStars = new List<KeyValuePair<KitType, int>>();
            foreach (var serviceStar in serviceStarsToken)
            {
                string[] parts = serviceStar.ToString().Split(':');
                var type = (KitType)Convert.ToInt32(parts[0].Substring(parts[0].IndexOf('"') + 1, parts[0].LastIndexOf('"') - 1));
                int count = Convert.ToInt32(Convert.ToDouble(parts[1], CultureInfo.InvariantCulture));
                serviceStars.Add(new KeyValuePair<KitType, int>(type, count));
            }
            return serviceStars;
        }

        // Basic BF3 soldier information
        private ISoldier ParseSoldier(JToken overviewStatsToken)
        {
            ISoldier soldier = new Soldier()
            {
                Rank = Convert.ToInt32(overviewStatsToken.SelectToken("rank").ToString()),
                Skill = Convert.ToDouble(overviewStatsToken.SelectToken("elo").ToString()), // , CultureInfo.InvariantCulture
                TimePlayed = TimeSpan.FromSeconds(Convert.ToDouble(overviewStatsToken.SelectToken("timePlayed").ToString(), CultureInfo.InvariantCulture)),
                ScorePerMinute = Convert.ToInt32(overviewStatsToken.SelectToken("scorePerMinute").ToString()),
                KillDeathRatio = Convert.ToDouble(overviewStatsToken.SelectToken("kdRatio").ToString()), // , CultureInfo.InvariantCulture
                Kills = Convert.ToInt32(overviewStatsToken.SelectToken("kills").ToString()),
                Wins = Convert.ToInt32(overviewStatsToken.SelectToken("numWins").ToString()),
                Losses = Convert.ToInt32(overviewStatsToken.SelectToken("numLosses").ToString()),
            };
            return soldier;
        }

        // Battlelog user information
        private IUser ParseUser(JToken userToken)
        {
            IUser user = new User
            {
                Id = Convert.ToInt64(userToken.SelectToken("userId").ToString()),
                Name = userToken.SelectToken("username").ToString(),
                Gravatar = userToken.SelectToken("gravatarMd5").ToString()
            };
            return user;
        }

        // Top 5 weapons
        private IItems ParseTopWeapons(JToken weaponsToken)
        {
            var topWeapons = new List<IItem>();
            foreach (var topWeapon in weaponsToken.SelectToken("topStats"))
            {
                string guid = topWeapon.SelectToken("guid").ToString();
                var weapon = new Item()
                    {
                        Name = topWeapon.SelectToken("name").ToString().ToUpperInvariant(),
                        Slug = topWeapon.SelectToken("slug").ToString().ToUpperInvariant(),
                        Kills = Convert.ToInt32(topWeapon.SelectToken("kills").ToString().ToUpperInvariant()),
                        Headshots = Convert.ToInt32(topWeapon.SelectToken("headshots").ToString().ToUpperInvariant()),
                        ServiceStars = Convert.ToInt32(topWeapon.SelectToken("serviceStars").ToString()),
                        Image = weaponsToken.SelectToken("bf3GadgetsLocale").SelectToken("weapons").SelectToken(guid).SelectToken("image").ToString()
                    };
                topWeapons.Add(weapon);
            }
            return new Items(topWeapons);
        }

        // Generic statistics
        private IStatistics ParseStatistics(JToken statisticsToken)
        {
            IStatistics statistics = new Statistics
            {
                Kills = Convert.ToInt32(statisticsToken.SelectToken("kills").ToString()),
                Deaths = Convert.ToInt32(statisticsToken.SelectToken("deaths").ToString()),
                KillAssists = Convert.ToInt32(statisticsToken.SelectToken("killAssists").ToString()),
                VehiclesDestroyed = Convert.ToInt32(statisticsToken.SelectToken("vehiclesDestroyed").ToString()),
                VehiclesDestroyedAssists = Convert.ToInt32(statisticsToken.SelectToken("vehiclesDestroyedAssists").ToString()),
                Accuracy = Convert.ToDouble(statisticsToken.SelectToken("accuracy").ToString()), // , CultureInfo.InvariantCulture
                LongestHeadshot = Convert.ToDouble(statisticsToken.SelectToken("longestHeadshot").ToString()), // CultureInfo.InvariantCulture
                KillStreakBonus = Convert.ToInt32(statisticsToken.SelectToken("killStreakBonus").ToString()),
                SquadScoreBonus = Convert.ToInt32(statisticsToken.SelectToken("sc_squad").ToString()),
                Repairs = Convert.ToInt32(statisticsToken.SelectToken("repairs").ToString()),
                Revives = Convert.ToInt32(statisticsToken.SelectToken("revives").ToString()),
                Heals = Convert.ToInt32(statisticsToken.SelectToken("heals").ToString()),
                Resupplies = Convert.ToInt32(statisticsToken.SelectToken("resupplies").ToString())
            };
            return statistics;
        }

        // Latest awards (ribbons and medals)
        private IAwards ParseAwards(IEnumerable<JToken> awardsToken)
        {
            var awards = new List<IAward>();
            foreach (var token in awardsToken)
            {
                var award = new Award()
                {
                    Code = token.SelectToken("award").SelectToken("code").ToString(),
                    Group = token.SelectToken("award").SelectToken("awardGroup").ToString(),
                    Image = token.SelectToken("award").SelectToken("code").ToString()
                };
                awards.Add(award);
            }
            return new Awards(awards);
        }

        // Top 5 vehicles (in another file)
        private IItems ParseTopVehicles(string file)
        {
            var vehicles = new List<IItem>();
            string completeJson;
            var resource = _isolatedStorage.OpenFile(file, FileMode.Open);
            using(var streamReader = new StreamReader(resource))
            {
                completeJson = streamReader.ReadToEnd();
            }
            JObject jObject = JObject.Parse(completeJson);

            foreach (var vehiclesToken in jObject.SelectToken("data").SelectToken("mainVehicleStats"))
            {
                string guid = vehiclesToken.SelectToken("guid").ToString();
                var vehicle = new Item()
                {
                    Name = vehiclesToken.SelectToken("name").ToString(),
                    Slug = (vehiclesToken.SelectToken("slug").ToString()).ToUpperInvariant().Replace("-", " "),
                    Kills = Convert.ToInt32(vehiclesToken.SelectToken("kills").ToString()),
                    TimeIn = TimeSpan.FromSeconds(Convert.ToInt32(vehiclesToken.SelectToken("timeIn").ToString())),
                    Image = jObject.SelectToken("data").SelectToken("bf3GadgetsLocale").SelectToken("vehicles").SelectToken(guid).SelectToken("image").ToString()
                };
                vehicles.Add(vehicle);
            }
            var sorted = vehicles.OrderByDescending(v => v.Kills).ThenByDescending(v => v.TimeIn).ThenBy(v => v.Slug).ThenBy(v => v.Name);
            return new Items(sorted.Take(5).ToList());
        }

        // Top 5 gadgets (in another file)
        private IItems ParseTopGadgets(string file)
        {
            var gadgets = new List<IItem>();
            string completeJson;
            using (var reader = new StreamReader(_isolatedStorage.OpenFile(file, FileMode.Open)))
            {
                completeJson = reader.ReadToEnd();
                reader.Close();
            }
            JObject jObject = JObject.Parse(completeJson);

            foreach (var gadgetsToken in jObject.SelectToken("data").SelectToken("mainItemStats"))
            {
                string guid = gadgetsToken.SelectToken("guid").ToString();
                var gadget = new Item()
                {
                    Name = gadgetsToken.SelectToken("slug").ToString(),
                    Slug = (gadgetsToken.SelectToken("slug").ToString()).ToUpperInvariant().Replace("-", " "),
                    Kills = Convert.ToInt32(gadgetsToken.SelectToken("performanceStatValue").ToString()),
                    Image = jObject.SelectToken("data").SelectToken("bf3GadgetsLocale").SelectToken("kititems").SelectToken(guid).SelectToken("image").ToString()
                };
                gadgets.Add(gadget);
            }
            var sorted = gadgets.OrderByDescending(v => v.Kills).ThenBy(v => v.Slug);
            return new Items(sorted.Take(5).ToList());
        }
    }
}
